#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<unistd.h>
#include<sys/types.h>
#include<sys/wait.h>
#include <sys/stat.h>
#include <fcntl.h>
#define MAX_ARGC 64
#define SIZE 1024
#define SEP " "
#define STREND '\0'
//重定向相关
#define NoneRedir -1
#define stdinRedir 0
#define stdoutRedir 1
#define AppendRedir 2

#define IgnSpace(buf,pos) do{ while(isspace(buf[pos])) pos++;}while(0)

//默认所有的指令都不需要重定向，需要了再修改
int redir_type = NoneRedir;
//记录重定向的文件
char*filename = NULL;


char env[SIZE];
char pwd[SIZE];
//命令行参数表
char* argv[MAX_ARGC];
//程序退出码
int lastcode=0;
//获取用户主机名
const char* HostName()
{
    //从环境变量中获取
    const char*hostname=getenv("HOSTNAME");
    if(hostname!=NULL)
    {
        return hostname;
    }
    else{
        return "None";    
    }
}

//获取用户名
const char* UserName()
{
    //从环境变量中获取
    const char*username=getenv("USER");
    if(username!=NULL)
    {
        return username;
    }
    else{
        return "None";    
    }
}

//获取当前工作目录
const char* CurrentWorkDir()
{
    //从环境变量中获取
    const char*mypwd=getenv("PWD");
    if(mypwd!=NULL)
    {
        return mypwd;
    }
    else{
        return "None";    
    }
}
//返回家目录
char* Home()
{
    return getenv("HOME");
}


//封装交互信息
int Interactive(char out[], int size)
{
    //输出提示符并获取用户输入的命令字符串
    //模拟bash打印的命令行提示符
    printf("[%s@%s %s]$ ",UserName(),HostName(),CurrentWorkDir());
    //获取用户输入的命令提示符
    //获取一行信息
    fgets(out,sizeof(char)*size,stdin);
    //将输入的一行信息结尾的'\n'替换为'\0'
    out[strlen(out)-1]='\0';//做修正
    return strlen(out);
}
void chekRdir(char in[])
{
    redir_type = NoneRedir;
    filename=NULL;
    //扫描字符串，检查是否存在>/>>/</<<,存在就将其改为'\0'
    //使切割操作切割前面的，将记录文件名的变量做修改。
    int pos=strlen(in)-1;
    while(pos>=0)
    {
        if(in[pos]=='>')
        {
            if(in[pos-1]=='>')
            {
                redir_type = AppendRedir;
                in[pos-1]=STREND;
                pos++;
                IgnSpace(in,pos);
                filename=in+pos;
                break;
            }else{
                //>
                redir_type = stdoutRedir;
                in[pos]=STREND;
                pos++;
                IgnSpace(in,pos);
                filename=in+pos;
                break;
            }
        }else if(in[pos]=='<')
        {
            //将这个'<'覆盖为'\0'，只切割'<'字符之前的字符串
            in[pos] = STREND;
            //设置此时需要输入重定向
            redir_type = stdinRedir;
            //更新文件名
            //跳过空格
            pos++;
            IgnSpace(in,pos);
            //更新文件名
            filename = in+pos;
            break;
        }else{
            pos--;
        }
    }
}

//封装切割字符串
void Split(char in[])
{
    //在切割命令行之前，检查是否为重定向
    chekRdir(in);
    //对命令行字符串进行切割
    int i=0;
    argv[i]=strtok(in,SEP);
    i++;
    //strtok函数切割失败就会返回NULL
    while(argv[i++]=strtok(NULL,SEP))
    {}//将这里写成赋值，是为了将commandline字符串按照" "分成argv表
    //这里最后一次切割会返回NULL，且将NULL赋值给argv表，while循环判断非NULL就继续，NULL就结束，最后一次返回NULL，所以将NULL赋值之后再终止。
    //给ls命令带上颜色选项
    if(strcmp("ls",argv[0])==0)
    {
        argv[i-1]="--color";
        argv[i]=NULL;
    }
}
//执行指令
void Execute()
{
    //执行命令-通过创建子进程
    pid_t id=fork();
    if(id==0)
    {
        //在执行之前必须要先检查是否为重定向
        //通过redir_type来确定
        int fd=-1;
        if(redir_type==stdinRedir)
        {
            //这代表我们需要从filename文件中读取数据
            //所以将文件描述符为0的文件地址修改为filename
            fd=open(filename,O_RDONLY);
            dup2(fd,0);
        }else if(redir_type==stdoutRedir)
        {
            //这里需要将数据写入到filename文件中去
            //将文件描述符为1的文件的地址修改为filename
            fd=open(filename,O_WRONLY|O_TRUNC|O_CREAT,0666);
            dup2(fd,1);
        }else if(redir_type==AppendRedir)
        {
            //这里需要追加写入
            fd=open(filename,O_WRONLY|O_CREAT|O_APPEND,0666);
            dup2(fd,1);
        }else{
            //什么都不做
        }
        //chile process
        //让子进程执行
        //将程序名称传进去，系统会去path中的路径去找可执行程序，再将argv表传入
        execvp(argv[0],argv);    
        //执行完之后就退出
        exit(1);
    }
    //父进程等待回收子进程获取退出信息并释放子进程
    int status=0;
    pid_t rid=waitpid(id,&status,0);//阻塞等待
    if(rid==id)
    {
        lastcode=WEXITSTATUS(status);
    }
    //printf("run done,rid:%d\n",rid);
}

//
int BulidinCmd()
{
    int ret=0;
    //检测是否为内建命令 是1 否0
    if(strcmp("cd",argv[0])==0)
    {
        //是内建命令
        ret=1;
        //target命令来获取当前路径，对NULL特殊处理到家目录
        char* target=argv[1];
        if(target==NULL)
        {
            target=Home();
        }
        //cd命令，先更改路径
        chdir(target);
        //针对cd ..命令，temp用来获取当前已经更改了的工作路径
        char temp[1024];
        getcwd(temp,1024);//获取当前工作路径
        snprintf(pwd,SIZE,"PWD=%s",temp);//将这个工作路径写入到pwd字符数组中，以固定的格式
        putenv(pwd);//导出PWD这个环境变量，改变这个进程的PWD环境变量。
    }else if(strcmp("export",argv[0])==0)
    {
        ret=1;
        if(argv[1]!=NULL)
        {
            strcpy(env,argv[1]);
            putenv(env);
        }
    }else if(strcmp("echo",argv[0])==0)
    {
        ret=1;
        if(argv[1]==NULL)
        {
            printf("\n");
        }else{
            if(argv[1][0]=='$')
            {
                if(argv[1][1]=='?')
                {
                    printf("%d\n",lastcode);
                    lastcode=0;
                }else{
                    char*e = getenv(argv[1]+1);
                    if(e!=NULL){
                    printf("%s\n",e);
                    }

            }
            }else{
                printf("%s\n",argv[1]);
            }
        }
    }
        return ret;
}
int main()
{
    while(1)
    {
        //接收命令行参数信息字符串
        char commandline[SIZE];
        //交互信息
        int n = Interactive(commandline,SIZE);//如果获取到的信息是空串，就继续执行下一个
        if(n==0)
        {
            continue;
        }
        //子串切割
        Split(commandline);
        n = BulidinCmd();//检测是否是内建命令，如果是，就不需要创建子进程执行，如果不是就继续创建子进程
        if(n==1)
        {
            continue;
        }
        //执行指令---创建子进程
        Execute();
    }







   // for(int i=0;argv[i];i++)
   // {
   //     printf("argv[%d]:%s %p\n",i,argv[i],argv[i]);
   // }
    //printf("commandline:%s\n",commandline);
    return 0;
}
